/**
 * Copyright 2010 The Regents of the University of California
 *
 *  All Rights Reserved
 */

/**
 *
 * File: arp-cache.c
 * Handling the arp requessts and response from the hosts. 
 */

#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>

#include "dcswitch.h"
#include "flow.h"
#include "learning-switch.h"
#include "ofpbuf.h"
#include "openflow/openflow.h"
#include "poll-loop.h"
#include "new-switch-handler.h"
#include "flow-insertion.h"
#include "queue.h"
#include "rconn.h"
#include "timeval.h"
#include "vconn.h"
#include "xtoxll.h"
#include "vlog.h"

#include "arp-handling.h"
#include "arp-cache.h"

#define THIS_MODULE VLM_arp_handling



static void handle_arp_reply(struct rconn* rconn, struct lswitch *sw, struct arp_packet* ar, uint16_t in_port);
static void handle_arp_request(struct rconn* rconn, struct lswitch *sw, struct arp_packet* arp, uint16_t in_port);
static void send_arp_reply(struct lswitch *sw, struct rconn* rconn, arp_cache_record *acr, uint8_t *dst_mac, uint32_t dst_ip, int out_port);
static void send_arp_request(struct lswitch *sw, struct rconn* rconn, uint8_t *src_mac, uint32_t src_ip, uint32_t dst_ip, int out_port);
static void send_broadcast_directive(int index, struct arp_packet *arp);
static void get_pmac_from_sw_index(int index, int in_port, uint8_t* src_pmac);
static void insert_L2_entries_for_edge(int sw_index, uint8_t* amac, uint8_t* pmac, int in_port);
static void insert_L3_entries_for_edge(int sw_index, uint32_t aip, uint32_t pip, int in_port);
static void modify_L2_entries_for_edge(int sw_index, uint8_t* amac, uint8_t* pmac, int in_port);
static void modify_L3_entries_for_edge(int sw_index, uint32_t aip, uint32_t pip, int in_port);
static void handle_gratuitous_arp(struct rconn* rconn, struct lswitch *sw, struct arp_packet *arp, uint16_t in_port);
int get_fattree_index_from_pmac(uint8_t* pmac);
void broadcast_arp_response(arp_cache_record *acr);


struct arp_packet *form_arp_packet(uint16_t opcode, uint8_t *eth_src, uint8_t *eth_dst, uint32_t p_src, uint32_t p_dst)
{
	arp_packet *arpPacket = (struct arp_packet *)malloc(sizeof(arp_packet));
    
	VLOG_DBG("[%s] started", __PRETTY_FUNCTION__);

	arpPacket->eth.eth_type = htons(0x0806);
	arpPacket->arp.ar_hrd = htons(0x0001);
	arpPacket->arp.ar_pro = htons(0x0800);
	arpPacket->arp.ar_hln = ETH_ADDR_LEN;
	arpPacket->arp.ar_pln = 4;
	arpPacket->arp.ar_op = opcode;
    memcpy(arpPacket->eth.eth_src, eth_src, ETH_ADDR_LEN);
	if (opcode == htons(0x0001) && eth_dst == NULL)
	{
    	memset(arpPacket->eth.eth_dst, 0xff, ETH_ADDR_LEN);
    	memset(&arpPacket->arp.ar_tha, 0x00, ETH_ADDR_LEN);
	}
	else
	{
    	memcpy(arpPacket->eth.eth_dst, eth_dst, ETH_ADDR_LEN);
    	memcpy(&arpPacket->arp.ar_tha, eth_dst, ETH_ADDR_LEN);
	}
    memcpy(&arpPacket->arp.ar_sha, eth_src, ETH_ADDR_LEN);
    arpPacket->arp.ar_spa = p_src;
    arpPacket->arp.ar_tpa = p_dst;
	VLOG_DBG("[%s] completed", __PRETTY_FUNCTION__);
	return (arpPacket);
}

void handle_arp(struct rconn* rconn, struct lswitch *sw, struct ofp_packet_in *opi)
{
	uint16_t in_port = ntohs(opi->in_port);
	struct arp_packet *arp = (struct arp_packet*)opi->data;
	VLOG_DBG("Handling arp packets from host %x to host %x", ntohl(arp->arp.ar_spa), ntohl(arp->arp.ar_tpa));

    if (arp->arp.ar_spa == arp->arp.ar_tpa) 
	{	
        VLOG_DBG("GRATUITOUS ARP for %x:%x:%x:%x:%x:%x  got!!!\n", arp->arp.ar_sha[0], arp->arp.ar_sha[1], arp->arp.ar_sha[2], arp->arp.ar_sha[3], arp->arp.ar_sha[4], arp->arp.ar_sha[5]);
		handle_gratuitous_arp(rconn, sw, arp, in_port);
	}

	else if(arp->arp.ar_op == htons(0x0001))
		handle_arp_request(rconn, sw, arp, in_port);
	else if(arp->arp.ar_op == htons(0x0002))
		handle_arp_reply(rconn, sw, arp, in_port);

}

void handle_gratuitous_arp(struct rconn* rconn, struct lswitch *sw, struct arp_packet *arp, uint16_t in_port)
{
	uint8_t src_amac[ETH_ADDR_LEN];
	uint8_t src_pmac[ETH_ADDR_LEN];
	uint32_t src_aip = ntohl(arp->arp.ar_spa);
	uint32_t dst_aip = ntohl(arp->arp.ar_tpa);
	uint32_t src_pip;
	int insert;
	int index = new_lookup(sw->datapath_id);
	arp_cache_record *acr;
	DCSwitch_node *node;
    VLOG_WARN("GRATUITOUS ARP for %x:%x:%x:%x:%x:%x  got host %x !!!\n", arp->arp.ar_sha[0], arp->arp.ar_sha[1], arp->arp.ar_sha[2], arp->arp.ar_sha[3], arp->arp.ar_sha[4], arp->arp.ar_sha[5], dst_aip);
	
	node = DCSwitches[index];
	if(node == NULL) 
	{ 
		VLOG_WARN("Node is NULL!!!");
		return;
	}
	if((fattree_index(sw->datapath_id))%K >=K/2 || fattree_index(sw->datapath_id)>= K*K)
		VLOG_WARN("Error only edge switch should make arp requests");
	else
		VLOG_DBG("Handling arp request from switch %d %d. Host ip: %x dl: %x:%x:%x:%x:%x:%x ", node->fattree_index, node->index, src_aip, src_amac[0], src_amac[1],src_amac[2], src_amac[3], src_amac[4], src_amac[5] );	


	memcpy(src_amac, arp->arp.ar_sha, ETH_ADDR_LEN);
	get_pmac_from_sw_index(index, in_port, src_pmac);
	src_pip = convert_to_uint_ip(src_pmac);

	insert = register_in_arp_cache(&arp_cache_table, src_aip, src_amac, src_pmac, src_pip);
	acr = arp_cache_entry(&arp_cache_table, src_aip);
	
	if(acr == NULL)
	{
		VLOG_WARN("Registering gratuitous arp for host %x failed", src_aip);
		return;
	}

	if(insert)
	{
		if(getFlowEntryMatching() == PMAC)
		{
			//TODO:
			modify_L2_entries_for_edge(index, src_amac, src_pmac, in_port);
			broadcast_arp_response(acr);

		}
		else if (getFlowEntryMatching() == PIP)
		{
			modify_L3_entries_for_edge(index, src_aip, src_pip, in_port);
			//TODO: also will need to modify flow entries in each edge switch which has an upward entry for this ip addr
		}
	}
}


void broadcast_arp_response(arp_cache_record *acr)
{
	int i, j;
	DCSwitch_node *node;
	uint8_t bcast_mac[ETH_ADDR_LEN];
	uint32_t bcast_ip = 0xffffffff;
	bcast_mac[0]= 0xff;
	bcast_mac[1]= 0xff;
	bcast_mac[2]= 0xff;
	bcast_mac[3]= 0xff;
	bcast_mac[4]= 0xff;
	bcast_mac[5]= 0xff;


	for(i=0; i< MAX_SWITCHES; i++)
	{
		node = DCSwitches[i];
		if(node!=NULL)
		if(node->level == 0)
		{
			for(j=0; j<K; j++)
				if(node->ports[j][1] == 0)
					send_arp_reply(node->sw, node->rconn, acr, bcast_mac, bcast_ip, node->ports[j][0]);
		}
	}
}

void forward_arp_to_all(arp_cache_record *acr)
{
	uint32_t dst_ip = 0xffffffff;
	uint8_t dst_mac[ETH_ADDR_LEN];
	int i, j;
	dst_mac[0] = 0xff;
	dst_mac[1] = 0xff;
	dst_mac[2] = 0xff;
	dst_mac[3] = 0xff;
	dst_mac[4] = 0xff;
	dst_mac[5] = 0xff;

	VLOG_WARN("Forward arp to all ");
	for(i=0; i< MAX_SWITCHES; i++)
	{
		DCSwitch_node* node = DCSwitches[i];
		if(node == NULL) continue;
		if(node->level != EDGE) continue;
		for(j=0; j<K; j++)
		{
			if(node->ports[j][1] == 0)
				send_arp_reply(node->sw, node->rconn, acr, dst_mac, dst_ip, node->ports[j][0]);
		}
	}
}

static void modify_L2_entries_for_edge(int sw_index, uint8_t* amac, uint8_t* pmac, int in_port)
{
	VLOG_WARN("Modify L2 entries");
}

void modify_L3_entries_for_edge(int sw_index, uint32_t aip, uint32_t pip, int in_port)
{
	VLOG_WARN("Modify L3 entries");
}

static void handle_arp_request(struct rconn* rconn, struct lswitch *sw, struct arp_packet* arp, uint16_t in_port)
{
	uint8_t src_amac[ETH_ADDR_LEN];
	uint8_t src_pmac[ETH_ADDR_LEN];
	uint32_t src_aip = ntohl(arp->arp.ar_spa);
	uint32_t dst_aip = ntohl(arp->arp.ar_tpa);
	uint32_t src_pip;
	int index = new_lookup(sw->datapath_id);
	arp_cache_record *acr;
	DCSwitch_node *node;
	int insert;

	VLOG_WARN("Handling arp requests from host %x to host %x", ntohl(arp->arp.ar_spa), ntohl(arp->arp.ar_tpa));
	node = DCSwitches[index];
	if(node == NULL) 
	{ 
		VLOG_WARN("Node is NULL!!!");
		return;
	}
	memcpy(src_amac, arp->arp.ar_sha, ETH_ADDR_LEN);

	if((fattree_index(sw->datapath_id))%K >=K/2 || fattree_index(sw->datapath_id)>= K*K)
		VLOG_WARN("Error only edge switch should make arp requests");
	else
		VLOG_DBG("Handling arp request from switch %d %d. Host ip: %x dl: %x:%x:%x:%x:%x:%x ", node->fattree_index, node->index, src_aip, src_amac[0], src_amac[1],src_amac[2], src_amac[3], src_amac[4], src_amac[5] );	


	get_pmac_from_sw_index(index, in_port, src_pmac);
	src_pip = convert_to_uint_ip(src_pmac);

	insert = register_in_arp_cache(&arp_cache_table, src_aip, src_amac, src_pmac, src_pip);
	VLOG_DBG("Aip = %x Pip = %x ", src_aip, src_pip);

	if(insert)
	{
		VLOG_DBG("Inserting entries for new src_ip  %x to src_pip %x in the edge switch %d %d", src_aip, src_pip, node->fattree_index, node->sw_indexTmp);
	if(getFlowEntryMatching() == PMAC)
		insert_L2_entries_for_edge(index, src_amac, src_pmac, in_port);
	else if(getFlowEntryMatching() == PIP)
		insert_L3_entries_for_edge(index, src_aip, src_pip, in_port);
	}
	
	acr = arp_cache_entry(&arp_cache_table, dst_aip);
	if(acr!=NULL)
		send_arp_reply(sw, rconn, acr, src_amac, src_aip, in_port);		
	else
		send_broadcast_directive(index, arp);

}


static void insert_L2_entries_for_edge(int sw_index, uint8_t* amac, uint8_t* pmac, int in_port)
{
	DCSwitch_node* node = DCSwitches[sw_index];
	VLOG_DBG("Inserting  L2 flow entries for edge %d", sw_index);
	insert_L2_entry(node->rconn, node->sw, amac, in_port);
	insert_modify_L2_entry(node->rconn, node->sw, amac, pmac, in_port);
}


static void insert_L3_entries_for_edge(int sw_index, uint32_t aip, uint32_t pip, int in_port)
{
	DCSwitch_node* node = DCSwitches[sw_index];
	VLOG_DBG("Inserting L3 flow entries for %x to %x in edge assigned ft %d actual ft %d index %d", aip, pip, node->fattree_index, node->sw_indexTmp, node->index);
	insert_L3_entry(node->rconn, node->sw, aip, in_port);
	insert_modify_L3_entry(node->rconn, node->sw, pip, aip, in_port);
}


void handle_arp_reply(struct rconn* rconn, struct lswitch *sw, struct arp_packet* arp, uint16_t in_port)
{
	uint8_t src_amac[ETH_ADDR_LEN];
	uint8_t src_pmac[ETH_ADDR_LEN];
	uint32_t src_aip = ntohl(arp->arp.ar_spa);
	int insert;
	uint32_t src_pip;


	int sw_index = new_lookup(sw->datapath_id);
	DCSwitch_node* node = DCSwitches[sw_index];
	VLOG_WARN("Handling arp response from host %x to host %x", ntohl(arp->arp.ar_spa), ntohl(arp->arp.ar_tpa));
	VLOG_DBG("Handling arp response from host %x on switch %d %d on port: %d", src_aip, node->fattree_index, node->sw_indexTmp, in_port );

	get_pmac_from_sw_index(sw_index, in_port, src_pmac);
	src_pip = convert_to_uint_ip(src_pmac);
	
	memcpy(src_amac, arp->arp.ar_sha, ETH_ADDR_LEN);

	insert = register_in_arp_cache(&arp_cache_table, src_aip, src_amac, src_pmac, src_pip);

	if(insert)
	{
	
	if(getFlowEntryMatching() == PMAC)
		insert_L2_entries_for_edge(sw_index, src_amac, src_pmac, in_port);
	else if(getFlowEntryMatching() == PIP)
		insert_L3_entries_for_edge(sw_index, src_aip, src_pip, in_port);
	}
}


void print_mac(uint8_t* mac)
{
	VLOG_DBG("Mac: %x:%x:%x:%x:%x:%x", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
}


void get_pmac_from_sw_index(int index, int in_port, uint8_t* src_pmac)
{
	DCSwitch_node *node = DCSwitches[index];
	int fattree_index = node->fattree_index;
	int port = -1 ,i;
	int pod = fattree_index/K;
	int position = (fattree_index)%K;

	for(i=0; i<K; i++)
		if(node->ports[i][0] == in_port)
			port = i;

	if(port == -1)
		VLOG_WARN("This switch %d doesn't have port %d", index, in_port);
	#ifdef VM
	src_pmac[0] = 0;
	src_pmac[1] = 1;
	src_pmac[2] = pod;
	src_pmac[3] = position;
	src_pmac[4] = port;
	src_pmac[5] = last_vm_id[index][port];
//	last_vm_id[index][port]+=1;
	#else
	src_pmac[0] = 0;
	src_pmac[1] = 1;
	src_pmac[2] = 1;
	src_pmac[3] = pod;
	src_pmac[4] = position;
	src_pmac[5] = port;
	#endif
	VLOG_DBG("Pmac from switch index %d fattree index %d ",index, fattree_index);


}	



void send_broadcast_directive(int index, struct arp_packet *arp)
{
	int i, j, from_edge;
	//struct ofpbuf *out;
	uint8_t src_mac[ETH_ADDR_LEN];
	uint32_t src_ip = ntohl(arp->arp.ar_spa);
	uint32_t dst_ip = ntohl(arp->arp.ar_tpa);

	arp_cache_record* acr = arp_cache_entry(&arp_cache_table, src_ip);
	DCSwitch_node* node;

	memcpy(src_mac, acr->pmac, ETH_ADDR_LEN);
	from_edge = get_fattree_index_from_pmac(acr->pmac);
	/*We send arp-request with source pmacs*/

	for(i=0; i< MAX_SWITCHES; i++)
	{
		node = DCSwitches[i];
		if(node!=NULL)
		if(node->level == 0)
		{
			for(j=0; j<K; j++)
			if(node->ports[j][1] == 0)
			{
				if(node->fattree_index == from_edge)
				{
					VLOG_DBG("Hosts connected to same edge switch %d. Sending request with amac", from_edge);
					memcpy(src_mac, acr->dl_addr, ETH_ADDR_LEN);
				}
				else
					memcpy(src_mac, acr->pmac, ETH_ADDR_LEN);


				send_arp_request(node->sw, node->rconn, src_mac, src_ip, dst_ip, node->ports[j][0]);
			}
		}
	}
}


void send_arp_request(struct lswitch *sw, struct rconn* rconn, uint8_t *src_mac, uint32_t src_ip, uint32_t dst_ip, int out_port)
{
//	VLOG_DBG("Sending arp request");

	arp_packet *ar = form_arp_packet(htons(0x0001), src_mac, NULL, htonl(src_ip), htonl(dst_ip));

	struct ofpbuf *arp_req = ofpbuf_new(sizeof(arp_packet));
	struct ofpbuf *out;
	ofpbuf_put(arp_req, ar, sizeof(arp_packet));

	out  = make_unbuffered_packet_out(arp_req, OFPP_NONE, out_port);
	queue_tx(sw, rconn, out);

}


void send_arp_reply(struct lswitch *sw, struct rconn* rconn, arp_cache_record *acr, uint8_t *dst_mac, uint32_t dst_ip, int out_port)
{

	//lookup_pmac_table(acr->dl_addr)
	//if sw->fattree_index == get_fattree_index_from_pmac()
	//send arp reply with amac else send arp reply with pmac
	int index = new_lookup(sw->datapath_id);
	int fattree_index = (DCSwitches[index])->fattree_index;
	int from_edge = get_fattree_index_from_pmac(acr->pmac);
	struct ofpbuf *arp_reply = ofpbuf_new(sizeof(arp_packet));
	struct ofpbuf *out;
	arp_packet *ar=NULL;
	VLOG_DBG("Sending arp reply from host %x to host %x on port %d", acr->nw_addr, dst_ip, out_port);

	VLOG_DBG("Edge switch for host %x is ft index %d index %d, edge switch for src_host %x is ft index %d", dst_ip, (DCSwitches[index])->fattree_index, index, acr->nw_addr, from_edge);

	if(fattree_index == from_edge)
	{
		VLOG_DBG("Hosts connected to same edge switch %d. Replying with amac", fattree_index);
		ar = form_arp_packet(htons(0x0002),  acr->dl_addr, dst_mac, htonl(acr->nw_addr), htonl(dst_ip));
	}
	else
	{
		if(getFlowEntryMatching() == PMAC)
		{
			VLOG_DBG("Hosts connected to different edge switches %d and %d. Replying with pmac", fattree_index, from_edge);
			ar = form_arp_packet(htons(0x0002),  acr->pmac, dst_mac, htonl(acr->nw_addr), htonl(dst_ip));
		}
		else if(getFlowEntryMatching() == PIP)
		{
			VLOG_DBG("Forwarding based on pip. Edge switches %d and %d. Replying with amac", fattree_index, from_edge);
			ar = form_arp_packet(htons(0x0002),  acr->dl_addr, dst_mac, htonl(acr->nw_addr), htonl(dst_ip));  //because you are not modifying pmacs
		}
		
	}

	ofpbuf_put(arp_reply, ar, sizeof(arp_packet));

	out  = make_unbuffered_packet_out(arp_reply, OFPP_NONE, out_port);
	queue_tx(sw, rconn, out);
}


int  get_fattree_index_from_pmac(uint8_t* pmac)
{	
	int fattree_index = 0;
	#ifdef VM
	fattree_index = pmac[2]*K + pmac[3];
	#else
	fattree_index = pmac[3]*K + pmac[4];
	#endif
	return fattree_index;	
}

uint32_t convert_to_pip_vm(uint8_t* pmac)
{
	uint32_t ip_add;
	ip_add = 0x0a << 24;
	ip_add += ((pmac[5] & 15) | (pmac[4]<<4)) + (pmac[3] << 8) + (pmac[2] <<16);
	return ip_add;
}

void init_arp_cache(void)
{
	int i, j, k;
	uint8_t mac[ETH_ADDR_LEN];
	uint32_t ip;

	
	for(i=0; i<K; i++)
	{
		for(j=0; j<K/2; j++)
		{
			for(k=0; k<K; k++)
			{

				mac[0] = 0;
				mac[1] = 1;
				mac[2] = 1;
				mac[3] = i;
				mac[4] = j;
				mac[5] = k;

				ip = convert_to_uint_ip(mac);
				register_in_arp_cache(&arp_cache_table, ip , mac, mac, ip);
			}
		}
	}
}
